/*
 * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
 * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *     * The WHITECAT logotype cannot be changed, you can remove it, but you
 *       cannot change it in any way. The WHITECAT logotype is:
 *
 *          /\       /\
 *         /  \_____/  \
 *        /_____________\
 *        W H I T E C A T
 *
 *     * Redistributions in binary form must retain all copyright notices printed
 *       to any local or remote output device. This include any reference to
 *       Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
 *       appear in the future.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Lua RTOS, a tool for make a LFS file system image
 *
 */


/*
 * Copyright (C) 2019 GreenWaves Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Authors: Mathieu Barbe, GreenWaves Technologies (mathieu.barbe@greenwaves-technologies.com)
 */


#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <dirent.h>
#include <sys/types.h>

#include "lfs/lfs.h"

static struct lfs_config cfg;
static lfs_t lfs;
static uint8_t *data;
static size_t fs_size;

static int lfs_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
{
	if(block * c->block_size + off + size > fs_size)
		return LFS_ERR_IO;
	memcpy(buffer, data + (block * c->block_size) + off, size);
	return LFS_ERR_OK;
}

static int lfs_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
{
	if(block * c->block_size + off + size > fs_size)
		return LFS_ERR_IO;
	memcpy(data + (block * c->block_size) + off, buffer, size);
	return LFS_ERR_OK;
}

static int lfs_erase(const struct lfs_config *c, lfs_block_t block)
{
	if(block * c->block_size + c->block_size > fs_size)
		return LFS_ERR_IO;
	memset(data + (block * c->block_size), 0xFF, c->block_size);
	return LFS_ERR_OK;
}

static int lfs_sync(const struct lfs_config *c)
{
	return LFS_ERR_OK;
}

static void create_dir(char *src)
{
	char *path;
	int ret;
	
	path = strchr(src, '/');
	if(path)
	{
		printf("%s\n", path);
		
		if((ret = lfs_mkdir(&lfs, path)) < 0)
		{
			fprintf(stderr, "can't create directory %s: error=%d\r\n", path, ret);
			exit(1);
		}
	}
}

static void create_file(char *src)
{
	char *path;
	int ret;
	
	path = strchr(src, '/');
	if(path)
	{
		printf("%s\n", path);
		
		// Open source file
		FILE *srcf = fopen(src, "rb");
		if(!srcf)
		{
			fprintf(stderr, "can't open source file %s: errno=%d (%s)\r\n", src, errno, strerror(errno));
			exit(1);
		}
		
		// Open destination file
		lfs_file_t dstf;
		if((ret = lfs_file_open(&lfs, &dstf, path, LFS_O_WRONLY | LFS_O_CREAT)) < 0)
		{
			fprintf(stderr, "can't open destination file %s: error=%d\r\n", path, ret);
			exit(1);
		}
		
		char c = fgetc(srcf);
		while (!feof(srcf))
		{
			ret = lfs_file_write(&lfs, &dstf, &c, 1);
			if(ret < 0)
			{
				fprintf(stderr, "can't write to destination file %s: error=%d\r\n", path, ret);
				exit(1);
			}
			c = fgetc(srcf);
		}
		
		// Close destination file
		ret = lfs_file_close(&lfs, &dstf);
		if(ret < 0)
		{
			fprintf(stderr, "can't close destination file %s: error=%d\r\n", path, ret);
			exit(1);
		}
		
		// Close source file
		fclose(srcf);
	}
}

static void compact(char *src)
{
	DIR *dir;
	struct dirent *ent;
	char curr_path[PATH_MAX];
	
	dir = opendir(src);
	if(dir)
	{
		while ((ent = readdir(dir)))
		{
			// Skip . and .. directories
			if((strcmp(ent->d_name, ".") != 0) && (strcmp(ent->d_name, "..") != 0))
			{
				// Update the current path
				strcpy(curr_path, src);
				strcat(curr_path, "/");
				strcat(curr_path, ent->d_name);
				
				if(ent->d_type == DT_DIR)
				{
					create_dir(curr_path);
					compact(curr_path);
				} else if(ent->d_type == DT_REG)
				{
					create_file(curr_path);
				}
			}
		}
		
		closedir(dir);
	}
}

void usage()
{
	fprintf(stdout,
	        "usage: mklfs -c <pack-dir> -b <block-size> -r <read-size> -p <prog-size> -s <filesystem-size> -i <image-file-path>\r\n");
}

static int is_number(const char *s)
{
	const char *c = s;
	
	while (*c)
	{
		if((*c < '0') || (*c > '9'))
		{
			return 0;
		}
		c++;
	}
	
	return 1;
}

static int is_hex(const char *s)
{
	const char *c = s;
	
	if(*c++ != '0')
	{
		return 0;
	}
	
	if(*c++ != 'x')
	{
		return 0;
	}
	
	while (*c)
	{
		if(((*c < '0') || (*c > '9')) && ((*c < 'A') || (*c > 'F')) && ((*c < 'a') || (*c > 'f')))
		{
			return 0;
		}
		c++;
	}
	
	return 1;
}

static int to_int(const char *s)
{
	if(is_number(s))
	{
		return atoi(s);
	} else if(is_hex(s))
	{
		return (int) strtol(s, NULL, 16);
	}
	
	return -1;
}

int main(int argc, char **argv)
{
	char *src = NULL;   // Source directory
	char *dst = NULL;   // Destination image
	int c;              // Current option
	int block_size = 0; // Block size
	int read_size = 0;  // Read size
	int prog_size = 0;  // Prog size
	int err;
	
	while ((c = getopt(argc, argv, "c:i:b:p:r:s:")) != -1)
	{
		switch (c)
		{
			case 'c':
				src = optarg;
				break;
			
			case 'i':
				dst = optarg;
				break;
			
			case 'b':
				block_size = to_int(optarg);
				break;
			
			case 'p':
				prog_size = to_int(optarg);
				break;
			
			case 'r':
				read_size = to_int(optarg);
				break;
			
			case 's':
				fs_size = to_int(optarg);
				break;
		}
	}
	
	if((src == NULL) || (dst == NULL) || (block_size <= 0) || (prog_size <= 0) ||
	   (read_size <= 0) || (fs_size <= 0))
	{
		usage();
		exit(1);
	}
	
	// Mount the file system
	cfg.read = lfs_read;
	cfg.prog = lfs_prog;
	cfg.erase = lfs_erase;
	cfg.sync = lfs_sync;
	
	cfg.block_size = block_size;
	cfg.read_size = read_size;
	cfg.prog_size = prog_size;
	cfg.block_count = fs_size / cfg.block_size;
	cfg.lookahead_size = 8;
	cfg.cache_size = cfg.block_size;
	cfg.context = NULL;
	cfg.block_cycles = 100;
	
	
	printf("Source directory: %s\n", src);
	printf("Block size: 0x%X\n", cfg.block_size);
	printf("Block count: 0x%X\n", cfg.block_count);
	
	data = malloc(fs_size);
	if(!data)
	{
		fprintf(stderr, "no memory for mount\r\n");
		return -1;
	}
	memset(data, 0xFF, fs_size);
	
	err = lfs_format(&lfs, &cfg);
	if(err < 0)
	{
		fprintf(stderr, "format error: error=%d\r\n", err);
		return -1;
	}
	
	err = lfs_mount(&lfs, &cfg);
	if(err < 0)
	{
		fprintf(stderr, "mount error: error=%d\r\n", err);
		return -1;
	}
	
	printf("\nBuild fs from:\n");
	compact(src);
	
	FILE *img = fopen(dst, "wb+");
	
	if(!img)
	{
		fprintf(stderr, "can't create image file: errno=%d (%s)\r\n", errno, strerror(errno));
		return -1;
	}
	
	fwrite(data, 1, fs_size, img);
	
	fclose(img);
	
	free(data);
	
	return 0;
}
